#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Token {
    Atom(f64),
    Op(char),
}

#[derive(Debug)]
pub struct Lexer {
    tokens: Vec<Token>,
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("token {0} not recognized")]
    UnrecognizedToken(String),
}

impl Lexer {
    pub fn new(input: &str) -> Result<Self, Error> {
        let mut tokens = Vec::new();
        let iter = input.chars().filter(|s| !s.is_whitespace());

        let mut buf = String::new();

        for ch in iter {
            match ch {
                '+' | '-' | '*' | '/' | '^' | '%' | '(' | ')' => {
                    if !buf.is_empty() {
                        let num = Self::parse_buf(&mut buf)?;
                        tokens.push(Token::Atom(num));
                    }
                    tokens.push(Token::Op(ch));
                }
                _ => buf.push(ch),
            }
        }
        if !buf.is_empty() {
            let num = Self::parse_buf(&mut buf)?;
            tokens.push(Token::Atom(num));
        }

        tokens.reverse();
        Ok(Lexer { tokens })
    }

    pub fn next(&mut self) -> Option<Token> {
        self.tokens.pop()
    }

    pub fn peek(&self) -> Option<&Token> {
        self.tokens.last()
    }

    fn parse_buf(buf: &mut String) -> Result<f64, Error> {
        let val = buf
            .parse::<f64>()
            .map_err(|_| Error::UnrecognizedToken(buf.to_string()));
        buf.clear();
        val
    }
}
